﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

using alib.Concurrency;
using alib.Debugging;

namespace agree
{
	public class DebuggerResetException : Exception { }

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class ChartDebugger : identity_propchange
	{
		public ChartDebugger(IIdentity prv)
			: base(prv)
		{
			this.bpmap = new Dictionary<string, Breakpoint>();
		}
		public GenControl ctrl;

		public event Action<Breakpoint, Object[]> BreakpointHit;

		public Breakpoint CurrentBreakpoint { get { return bpx_cur; } }

		Dictionary<String, Breakpoint> bpmap;

		Breakpoint bpx_cur;

		bool f_autostop = true;

		bool f_cancel = false;

		bool f_pulse = false;

		public Object serialize_all_bps = new Object();

		public void request_reset()
		{
			if (bpx_cur == null)
				do_reset();
			else
			{
				f_cancel = true;
				bpx_cur.Enable = false;
			}
		}

		void do_reset()
		{
			var ktrl = ctrl;
			BreakpointHit = null;
			bpx_cur = null;
			lock (bpmap)
				bpmap.Clear();
			f_cancel = false;
			f_autostop = true;
			f_pulse = false;
			if (ktrl != null)
				ktrl.CancelJob(new DebuggerResetException());
		}

		[Conditional("DEBUG")]
		public void bp(String name, params Object[] args)
		{
			Breakpoint bpx;
			lock (bpmap)
				if (!bpmap.TryGetValue(name, out bpx))
				{
					bpx = new Breakpoint(this, name);
					bpx.Enable = f_autostop || name == "here's the result" || name == "error";
					bpmap.Add(name, bpx);
				}

			lock (serialize_all_bps)	// serialize halting across all breakpoints
			{
				var ev = BreakpointHit;
				if (ev != null) ev(bpx, args);
				bpx.Wait();
			}
		}

		public void Pulse()
		{
			if (bpx_cur != null)
			{
				f_pulse = true;
				bpx_cur.mre.Set();
			}
		}

		public void run_to_end()
		{
			f_autostop = false;
			lock (bpmap)
				foreach (var bpx in bpmap.Values)
					if (bpx.Enable)
					{
						bpx.Enable = false;
						bpx.NotifyPropertyChanged("Enable");
					}
		}

		void SetCurrentBreakpoint(Breakpoint bpx)
		{
			if (bpx != bpx_cur)
			{
				bpx_cur = bpx;
				base.NotifyPropertyChanged("CurrentBreakpoint");
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public class Breakpoint : INotifyPropertyChanged
		{
			static int next_id = 0;

			[MethodImpl(MethodImplOptions.NoInlining)]
			public Breakpoint(ChartDebugger cd, String name)
			{
				this.cd = cd;
				this.id = next_id++;
				this.mre = new ManualResetEvent(false);
				this.name = name;
				var frame = new StackTrace(3, true).GetFrame(0);
				var mi = frame.GetMethod();
				this.loc = String.Format("{0}.{1}", mi.DeclaringType.Name, mi.Name);
				this.filename = Path.GetFileName(frame.GetFileName());
				this.line_no = frame.GetFileLineNumber();
			}
			internal ManualResetEvent mre;
			readonly ChartDebugger cd;
			readonly int id;
			readonly String name;
			readonly String loc;
			readonly String filename;
			readonly int line_no;
			int hit_count;

			public int Id { get { return id; } }
			public String Name { get { return name ?? "bp " + id.ToString(); } }
			public String Location { get { return loc; } }
			public String Filename { get { return filename; } }
			public int LineNumber { get { return line_no; } }
			public int HitCount { get { return hit_count; } }

			public void NotifyPropertyChanged(String sev)
			{
				var ev = PropertyChanged;
				if (ev != null)
					ev(this, new PropertyChangedEventArgs(sev));
			}

			public void Wait()
			{
				if (cd.f_cancel)
					cd.do_reset();
				hit_count++;
				NotifyPropertyChanged("HitCount");
				if (Enable)
				{
					IsHit = true;
					mre.WaitOne();
					IsHit = false;
					if (cd.f_cancel)
						cd.do_reset();
					if (cd.f_pulse)
					{
						cd.f_pulse = false;
						mre.Reset();
					}
				}
			}

			public bool IsHit
			{
				get { return cd.CurrentBreakpoint == this; }
				private set
				{
					cd.SetCurrentBreakpoint(value ? this : null);
					NotifyPropertyChanged("IsHit");
					NotifyPropertyChanged("RedNotRed");
				}
			}

			public String RedNotRed
			{
				get { return cd.CurrentBreakpoint == this ? "Red" : "White"; }
			}

			public bool Enable
			{
				get { return !mre.WaitOne(0); }
				set { if (value) mre.Reset(); else mre.Set(); }
			}

			public event PropertyChangedEventHandler PropertyChanged;
		};

	};
}
